Skip to content

Do not follow HTTP redirects in the API and inference clients - #102

Draft
yutkin wants to merge 4 commits into
masterfrom
fix/no-follow-redirects
Draft

yutkin wants to merge 4 commits into
masterfrom
fix/no-follow-redirects

Conversation

@yutkin

@yutkin yutkin commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The SDK clients do not follow HTTP redirects by default. A 3xx response raises an error.

 InferenceClient._make_request
 AsyncInferenceExecution.status_json / result
-  session.request(...)                      # follows 3xx
+  InferenceClient._send(...)
+    session.request(..., allow_redirects=False)
+    3xx      -> raise InferenceClientError
+    4xx/5xx  -> raise InferenceClientError
 HTTPClient._request
-  requests.request(...)                     # follows 3xx
+  requests.request(..., allow_redirects=False)
+  handle_error: 3xx -> raise APIException
 AuthenticationService.authenticate / refresh
-  requests.post(...)
+  requests.post(..., allow_redirects=False)

InferenceClient has a new opt-in argument, follow_redirects (default False). When it is on, the client follows a redirect only if all of these are true:

  • The target has the same scheme, host and port as endpoint_base_url.
  • The target path is the deployment path, or it starts with <deployment path>/.
  • The Location path has no relative segment, plain or encoded.
  • The part of the target path under the deployment passes the same check as path. Forms such as ..;/, %-escapes and \ are rejected. The query string is not checked.
  • The client followed fewer than 5 redirects.

The client raises InferenceClientError for any other redirect. requests method rules apply: 303 changes to GET, 301 and 302 change POST to GET, and 307 and 308 keep the method and body. status_json() and result() never follow a redirect. HTTPClient and the authentication calls do not get the option.

When a redirect only adds or removes a trailing slash, the error message tells you to change path.

status_json() and result() also use the client timeout_seconds now.

Evidence

Unit test, for each client method and each status 301, 302, 303, 307 and 308:

server: POST /predict -> 307, Location: /moved
call:   client.run_sync(data, path='predict')
  • Before: the client sends a second request to /moved. The test fails.
    After: the client sends one request and raises InferenceClientError. The test passes.

Unit tests for follow_redirects=True:

  • A target inside the deployment is followed. A 307 or 308 keeps the method and body. A 301, 302 or 303 changes POST to GET.
  • These targets are rejected after one request: another deployment, a deployment name with the same prefix, .., %2e%2e, ..%2F, ..;/ and ..%252f segments, characters that path does not allow, another host, http instead of https, another port, and user info.
  • The sixth redirect raises an error.
  • status_json() and result() do not follow a redirect.

Tested locally:

  • uv run pytest tests/unit_tests: 624 passed, with security: check the inference request path with the API path rules #103 merged.
  • uv run ruff check and uv run ruff format --check: pass.
  • A FastAPI app and a Flask app behind a local reverse proxy. A request with a trailing-slash mismatch gets a 307 or 308. Before, the client followed the redirect and got 404. After, the client raises an error that shows the Location. A request without a mismatch gets 200 before and after.

Merge Danger

Door: two-way

A revert restores the old behavior.

Blast Radius: consumers

These cases can break:

  • Code that relies on a redirect. For example, an http:// base URL that the server redirects to https://. The call now raises an error. follow_redirects=True does not help here, because the scheme changes.
  • A container that redirects to add or remove a trailing slash. The call now raises an error. The caller must correct the path. If the container sends a Location inside the deployment, InferenceClient(..., follow_redirects=True) restores the old behavior.
  • status_json() and result() now raise on a 4xx or 5xx response. They also stop after timeout_seconds (default 300 seconds).
  • HTTPClient methods ignore allow_redirects=True.

🤖 Generated with Claude Code

yutkin and others added 4 commits October 29, 2026 16:18
InferenceClient, HTTPClient and the authentication token requests now
send requests with allow_redirects=False. A 3xx response raises
InferenceClientError or APIException.

AsyncInferenceExecution.status_json() and result() now use the client
timeout and raise on an HTTP error status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InferenceClient(follow_redirects=True) follows a redirect only if the
target is inside the deployment: same scheme, host and port as
endpoint_base_url, a path under the deployment path, and no relative
path segment in the Location path. It follows at most 5 redirects.
Any other redirect raises InferenceClientError. The default stays off.

AsyncInferenceExecution status and result never follow a redirect.
HTTPClient and the authentication calls do not change.

When a redirect only adds or removes a trailing slash, the error
message tells the caller to change path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ects-34af24

# Conflicts:
#	CHANGELOG.md
#	tests/unit_tests/inference_client/test_inference_client.py
With follow_redirects on, the part of the target path under the
deployment must now pass check_request_path with the same allowlist as
path. Forms such as `..;/`, `%`-escapes and `\` are rejected. The query
string is not checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant